import * as THREE from '../../build/three.module.js';
import Stats from '../../jsm/libs/stats.module.js';

import { FirstPersonControls } from '../../jsm/controls/FirstPersonControls.js';
import { ImprovedNoise } from '../../jsm/math/ImprovedNoise.js';
import { BufferGeometryUtils } from '../../jsm/utils/BufferGeometryUtils.js';

/**
 * 此函数使用噪声算法模拟的山川纹理，计算得到一个(x,z)点上对应的y点坐标值
 * 即地形高度值
 */
const generateHeight = (width, height) => {
  const data = [],
        perlin = new ImprovedNoise(),
        size = width * height,
        z = Math.random() * 100;

  let quality = 2;

  for(let j = 0; j < 4; j += 1) {
    if (j === 0) for(let i = 0; i < size; i += 1) data[i] = 0;

    for(let i = 0; i < size; i += 1) {
      let x = i % width, y = (i / width) | 0;
      // 柏林噪音算法，用于生成山脉地形模拟数据
      data[i] += perlin.noise(x / quality, y / quality, z) * quality;
    }

    quality *= 4;
  }

  return data;
}

/**
 * 从高度数据列表 data 中获取指定 x z 坐标处的高度值
 */
const getY = (x, z) => {
  return (data[x + z * worldWidth] * .2) | 0;
}

const worldWidth = 128, worldDepth = 128;
const worldHalfWidth = worldWidth / 2;
const worldHalfDepth = worldDepth / 2;
const data = generateHeight(worldWidth, worldDepth);

// 场景构建
const scene = new THREE.Scene();
scene.background = new THREE.Color( 0xbfd1e5 );

const camera = new THREE.PerspectiveCamera(65, innerWidth / innerHeight, 1, 20000);
camera.position.y = getY(worldHalfWidth, worldHalfDepth) * 100 + 100;

// 模型构建
const matrix = new THREE.Matrix4(); // 变换矩阵

const pxGeometry = new THREE.PlaneBufferGeometry(100, 100);
pxGeometry.attributes.uv.array[1] = .5;
pxGeometry.attributes.uv.array[3] = .5;
pxGeometry.rotateY(Math.PI / 2);
pxGeometry.translate(50, 0, 0);

var nxGeometry = new THREE.PlaneBufferGeometry( 100, 100 );
nxGeometry.attributes.uv.array[ 1 ] = 0.5;
nxGeometry.attributes.uv.array[ 3 ] = 0.5;
nxGeometry.rotateY( - Math.PI / 2 );
nxGeometry.translate( - 50, 0, 0 );

const pyGeometry = new THREE.PlaneBufferGeometry(100, 100);
pyGeometry.attributes.uv.array[5] = .5;
pyGeometry.attributes.uv.array[7] = .5;
pyGeometry.rotateX( - Math.PI / 2 );
pyGeometry.translate( 0, 50, 0 );

var pzGeometry = new THREE.PlaneBufferGeometry( 100, 100 );
pzGeometry.attributes.uv.array[ 1 ] = 0.5;
pzGeometry.attributes.uv.array[ 3 ] = 0.5;
pzGeometry.translate( 0, 0, 50 );

var nzGeometry = new THREE.PlaneBufferGeometry( 100, 100 );
nzGeometry.attributes.uv.array[ 1 ] = 0.5;
nzGeometry.attributes.uv.array[ 3 ] = 0.5;
nzGeometry.rotateY( Math.PI );
nzGeometry.translate( 0, 0, - 50 );

const geometries = [];

for(let z = 0; z < worldDepth; z += 1) {
  for(let x = 0; x < worldWidth; x += 1) {
    
    const h = getY(x,z);

    matrix.makeTranslation(
      x * 100 - worldHalfWidth * 100,
      h * 100,
      z * 100 - worldHalfDepth * 100
    )

    const px = getY(x + 1, z);
    const nx = getY(x - 1, z);
    const pz = getY(x, z + 1);
    const nz = getY(x, z - 1);

    geometries.push(pyGeometry.clone().applyMatrix(matrix));

    if ((px !== h && px !== h + 1) || x === 0) {
      geometries.push(pxGeometry.clone().applyMatrix(matrix));
    }

    if ((nx !== h && nx !== h + 1) || x === worldWidth - 1) {
      geometries.push(nxGeometry.clone().applyMatrix(matrix));
    }

    if ((pz !== h && pz !== h + 1) ||z === worldDepth - 1) {
      geometries.push(pzGeometry.clone().applyMatrix(matrix));
    }
    
    if ((nz !== h && nz !== h + 1) || z === 0) {
      geometries.push(nzGeometry.clone().applyMatrix(matrix));
    }

  }
}

// 几何体
const gmt = BufferGeometryUtils.mergeBufferGeometries(geometries);
gmt.computeBoundingSphere();

// 平面几何体的纹理贴图
const textrue = new THREE.TextureLoader().load('../../textures/minecraft/atlas.png');
textrue.magFilter = THREE.NearestFilter;

// 网格对象
const mesh = new THREE.Mesh(gmt, new THREE.MeshLambertMaterial({ map: textrue, side: THREE.DoubleSide }));
scene.add(mesh);

// 环境光
const al = new THREE.AmbientLight(0xcccccc);
scene.add(al);

// 平行光
const directionLight = new THREE.DirectionalLight(0xffffff, 2);
directionLight.position.set(1, 1, .5).normalize();
scene.add(directionLight);

// 辅助线
const axesH = new THREE.AxesHelper(20000);
scene.add(axesH);

const renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setPixelRatio(devicePixelRatio);
renderer.setSize(innerWidth, innerHeight);
document.body.appendChild(renderer.domElement);

const controls = new FirstPersonControls(camera, renderer.domElement);
controls.movementSpeed = 1000;
controls.lookSpeed = 0.125;
controls.lookVertical = true;

const stats = new Stats();
document.body.appendChild(stats.dom);

const clock = new THREE.Clock();

const render = () => {
  controls.update(clock.getDelta());
  renderer.render(scene, camera);
}

const animate = () => {
  requestAnimationFrame(animate);

  stats.update();
  render();
}
animate();

// 事件
const handleWindowResize = () => {
  camera.aspect = innerWidth / innerHeight;
  camera.updateProjectionMatrix();

  renderer.setSize(innerWidth, innerHeight);

  controls.handleResize();
}
window.addEventListener('resize', handleWindowResize);
